package lol.clann.bukkit.pluginboot.util;

import lol.clann.clannboot.annotation.inject.Inject;
import lol.clann.clannboot.annotation.svc.PostInstance;
import lol.clann.clannboot.annotation.svc.Service;
import lol.clann.clannboot.util.clazz.*;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import org.nutz.lang.Lang;

import javax.annotation.Nonnull;
import java.lang.reflect.Constructor;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * @author pyz
 * @date 2018/7/22 下午6:39
 */
@Service(priority = Integer.MAX_VALUE)
public class PlayerUtils {

    public static final WrapClass classCraftPlayer = ClassUtils.forName("{cb}.entity.CraftPlayer");
    public static final WrapClass classCraftEntity = ClassUtils.forName("{cb}.entity.CraftEntity");
    public static final WrapClass classNMSEntityPlayer = ClassUtils.forName("{nms}.EntityPlayer");
    public static final WrapClass classNMSEntityLiving = ClassUtils.forName("{nms}.EntityLiving");
    public static final WrapClass classNMSEntityHuman = ClassUtils.forName("{nms}.EntityHuman");
    public static final WrapClass classNMSPlayerInteractManager = ClassUtils.forName("{nms}.PlayerInteractManager");
    public static final WrapClass classNMSPlayerConnection = ClassUtils.forName("{nms}.PlayerConnection");
    public static final WrapClass classNMSNetworkManager = ClassUtils.forName("{nms}.NetworkManager");
    public static final WrapClass classGameProfile = ClassUtils.forName("com.mojang.authlib.GameProfile");
    public static final WrapClass classChannel = ClassUtils.forName("io.netty.channel.Channel");

    @Inject
    private ItemStackUtils itemUtils;
    @Inject
    private EntityUtils entityUtils;
    @Inject
    private InventoryUtils inventoryUtils;
    @Inject
    private WorldUtils worldUtils;
    @Inject
    private ServerUtils serverUtils;
    @Inject
    private PacketUtils packetUtils;
    private WrapField entity;
    private WrapField playerNetServerHandler;
    private WrapField inventory;
    private WrapField theInventoryEnderChest;

    private WrapMethod sendPacket;
    private WrapField channel;
    private WrapMethod closeScreen;
    private WrapMethod clearActivePotions;
    private WrapField netManager;
    private WrapMethod<Player> getEntity;
    private Constructor newGameProfile;

    public static void main(String[] args) {
        UUID uuid = new PlayerUtils().getOfflineUUID("lalala521");
        System.out.println(uuid.toString());
    }

    public static void toggleFly(Player p, boolean b) {
        p.setFallDistance(0.0F);
        if (b) {
            p.setAllowFlight(true);
        } else {
            p.setAllowFlight(false);
            p.setFlying(b);
        }
    }

    @PostInstance
    private void init() {
        entity = classCraftPlayer.getField("entity");
        playerNetServerHandler = classNMSEntityPlayer.getField(FieldFilter.build().type(classNMSPlayerConnection));
        inventory = classNMSEntityPlayer.getField(FieldFilter.build().type(InventoryUtils.classNMSPlayerInventory));
        theInventoryEnderChest = classNMSEntityPlayer.getField(FieldFilter.build().type(InventoryUtils.classNMSInventoryEnderChest));
        netManager = classNMSPlayerConnection.getField(FieldFilter.build().type(classNMSNetworkManager));
        channel = classNMSNetworkManager.getField(FieldFilter.build().type(classChannel));
        closeScreen = classNMSEntityPlayer.getMethod(MethodFilter.build().name("closeInventory", "closeScreen"));
        clearActivePotions = classNMSEntityPlayer.getMethod(MethodFilter.build().name("removeAllEffects", "clearActivePotions").emptyParameter());
        sendPacket = classNMSPlayerConnection.getMethod("sendPacket");
        getEntity = classCraftEntity.getMethod("getEntity");
        newGameProfile = classGameProfile.getConstructor(ParameterCondition.of(new Class[]{UUID.class, String.class}));
    }

    public Object getNetworkManager(Player player) {
        Object nms = toNMS(player);
        Object connection = playerNetServerHandler.get(nms);
        return netManager.get(connection);
    }

    public Object getPlayerConnection(Player player) {
        Object nms = toNMS(player);
        return playerNetServerHandler.get(nms);
    }

    public void sendPluginMessage(Player player, String channel, byte[] message) {
        sendPluginMessage(player, PluginUtils.getCallerPlugin(), channel, message);
    }

    public void sendPluginMessage(Player player, Plugin plugin, String channel, byte[] message) {
        player.sendPluginMessage(plugin, channel, message);
    }

    /**
     * 计算离线UUID
     *
     * @param playerName
     * @return
     */
    public UUID getOfflineUUID(String playerName) {
        return UUID.nameUUIDFromBytes(("OfflinePlayer:" + playerName).getBytes(StandardCharsets.UTF_8));
    }

    public Object getNmsInventory(Object player) {
        return inventory.get(player instanceof Player ? toNMS((Player) player) : player);
    }

    public Object getNmsEnderChest(Object player) {
        return theInventoryEnderChest.get(player instanceof Player ? toNMS((Player) player) : player);
    }

    /**
     * 转换为nms玩家对象
     *
     * @param player cb玩家 or nms玩家
     * @return
     */
    public Object toNMS(Object player) {
        if (player == null) {
            return null;
        }
        if (player instanceof Player) {
            return entity.get(player);
        }
        return player;
    }

    public Player toBukkit(Object player) {
        return getEntity.invoke(null, serverUtils.getServer(), player);
    }

    public Map<Integer, ItemStack> getInventoryContent(Player player) {
        return inventoryUtils.getInventoryContent(player.getInventory());
    }

    @Nonnull
    public List<Integer> getInventoryEmpySlot(Player p) {
        return inventoryUtils.getInventoryEmpySlot(p.getInventory());
    }

    /**
     * 计算玩家背包空格子数量
     *
     * @param p
     * @return
     */
    public int getInventoryEmpySlotCount(Player p) {
        return getInventoryEmpySlot(p).size();
    }

    public void updateHeldItemSlot(Player p) {
        p.getInventory().setHeldItemSlot(p.getInventory().getHeldItemSlot());
    }

    public void setHeldItemSlot(Player p, int i) {
        p.getInventory().setHeldItemSlot(i);
    }

    /**
     * 因为某些原因,客户端和服务端玩家快捷栏位置可能不一样
     * 此方法能同步客户端的快捷栏位置
     *
     * @param player
     */
    public void refreshHotbarIndex(Player player) {
        sendPacket(player, packetUtils.newPacketPlayOutHeldItemSlot(player.getInventory().getHeldItemSlot()));
    }

    /**
     * 同步指定位置的方块
     *
     * @param player
     * @param x
     * @param y
     * @param z
     */
    public void updateBlock(Player player, int x, int y, int z) {
        Object packet = packetUtils.newPacketPlayOutBlockChange(x, y, z, player.getWorld());
        sendPacket(player, packet);
    }

    public void sendPacket(Player p, Object packet) {
        Object net = playerNetServerHandler.get(toNMS(p));
        sendPacket.invoke(net, packet);
    }

    public String getServerIp(Player p) {
        Object netManager = getNetworkManager(p);
        Object channel = this.channel.get(netManager);
        String address = Invoker.of(channel)
                .invoke("localAddress")
                .invoke("toString")
                .get();
        return address.replaceFirst("/", "").split(":", 2)[0];
    }

    public String getPlayerIp(Player p) {
        return p.getAddress().getHostString().replaceFirst("/", "");
    }

    //将OfflinePlayer转换为Player
    public Player getOnlinePlayer(OfflinePlayer p) {
        if (p == null) {
            return null;
        }
        Player target = serverUtils.getPlayer(p.getName());
        if (target != null) {
            return target;
        }
        Object nmsServer = serverUtils.getNMSServer();
        Object nmsWorld = worldUtils.toNMS(Bukkit.getWorlds().get(0));
        Object itemInWorldManager = Invoker.of(ItemStackUtils.classNMSPlayerInteractManager.getHandle())
                .newInstance(new ParameterCondition(new Class[]{WorldUtils.classNMSWorld.getHandle()}), nmsWorld)
                .get();

        Object profile;
        Object nmsPlayer;
        try {
            profile = newGameProfile.newInstance(p.getUniqueId(), p.getName());
            nmsPlayer = classNMSEntityPlayer.getConstructor(ParameterCondition.of(
                    new Class[]{ServerUtils.classNMSMinecraftServer.getHandle(),
                            WorldUtils.classNMSWorldServer.getHandle(),
                            classGameProfile.getHandle(),
                            ItemStackUtils.classNMSPlayerInteractManager.getHandle()}))
                    .newInstance(nmsServer, nmsWorld, profile, itemInWorldManager);
        } catch (Exception e) {
            throw Lang.wrapThrow(e);
        }
        target = (nmsPlayer == null) ? null : toBukkit(nmsPlayer);
        if (target == null) {
            return null;
        }
        target.loadData();
        return target;
    }

    public Player getOnlinePlayer(String name) {
        if (name != null) {
            OfflinePlayer op = Bukkit.getOfflinePlayer(name);
            if (op != null) {
                return getOnlinePlayer(op);
            }
        }
        return null;
    }

    public boolean isOp(CommandSender sender) {
        return sender.isOp() && sender instanceof Player;
    }

    public boolean isCommonPlayer(CommandSender sender) {
        return sender instanceof Player && !sender.isOp();
    }

    public boolean isConsole(CommandSender sender) {
        return !(sender instanceof Player);
    }

    public boolean isPlayer(CommandSender sender) {
        return sender instanceof Player;
    }

    public void clearActivePotions(Player p) {
        clearActivePotions.invoke(toNMS(p));
    }

    public void closeScreen(Player p) {
        closeScreen.invoke(toNMS(p));
    }

    public boolean isPlayerExist(String player) {
        OfflinePlayer o = Bukkit.getOfflinePlayer(player);
        return o != null;
    }
}
